/**
 * 
 */
package io.netty.example.test.my;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.example.test.SocketConstants;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;

import java.net.InetSocketAddress;
import java.util.logging.Level;


/**
 * @author John zhang
 * @version 0.1
 */
public class DelegateClient {
	
	private static final LogLevel logLevel = LogLevel.DEBUG;
	private static final int READ_TIMEOUT = 1;
	private static final String ENCODER_DILIMITER = "\r\n";
	
	private Bootstrap clientBootstrap = new Bootstrap ();
	private DelegateClientChannelInBoundHandler ioHandler = new DelegateClientChannelInBoundHandler ();
	private Channel channel;
	
	private String serverAddress;
	private int serverPort;
	
	public DelegateClient ( String serverAddress, int serverPort ) {
		this.serverAddress = serverAddress;
		this.serverPort = serverPort;
	}
	
	public void connect () {
		final EventLoopGroup group = new NioEventLoopGroup();
		clientBootstrap.group ( group );
		clientBootstrap.channel ( NioSocketChannel.class );
		clientBootstrap.handler ( new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel( SocketChannel ch ) throws Exception {
            	ChannelPipeline pipeline = ch.pipeline();
            	pipeline.addLast ( "logger", 		new LoggingHandler( logLevel ) );
            	pipeline.addLast ( "idleState", 	new IdleStateHandler( READ_TIMEOUT, 0, 0 ) );
            	pipeline.addLast ( "framer", 		new DelimiterBasedFrameDecoder( 8192, Delimiters.lineDelimiter() ) );
            	pipeline.addLast ( "encoder", 		new StringEncoder ( CharsetUtil.UTF_8 ) );
            	pipeline.addLast ( "decoder", 		new StringDecoder ( CharsetUtil.UTF_8 ) );
            	pipeline.addLast ( "outBound", 		new DelegateClientChannelOutBoundHandler() );
            	pipeline.addLast ( "inBound", 		ioHandler );
            }
         } );
		clientBootstrap.option ( ChannelOption.SO_KEEPALIVE, true );
		try {
			ChannelFuture cf = clientBootstrap.connect ( new InetSocketAddress( serverAddress, serverPort ) ).sync ();
			channel = cf.channel ();
			cf.channel ().closeFuture ().sync ();
		} catch ( InterruptedException e ) {
			e.printStackTrace();
		} finally {
            group.shutdownGracefully();
        }
	}
	
	public boolean isConnected () {
		if ( channel == null ) {
			return false;
		}
		return channel.isActive ();
	}
	
	public void asyncWriteMessage ( Object message ) {
		if ( message == null ) {
			return;
		}
		if ( channel == null ) {
			connect ();
			if ( !isConnected () ) {
				return;
			}
		}
		channel.writeAndFlush ( message + ENCODER_DILIMITER );
	}
	
	public void asyncWriteMessage ( String action, String message ) {
		if ( message == null || action == null ) {
			return;
		}
		if ( channel == null ) {
			connect ();
			if ( !isConnected () ) {
				return;
			}
		}
		ByteBuf buffer = channel.alloc ().buffer ( 64 * 1024, 1024 * 1024 );
		buffer.writeBytes ( action.getBytes () );
		buffer.writeBytes ( SocketConstants.ACTION_DATA_DELIMITER.getBytes () );
		buffer.writeBytes ( message.getBytes () );
		buffer.writeBytes ( ENCODER_DILIMITER.getBytes () );
		channel.writeAndFlush ( buffer );
		
		channel.attr ( SocketConstants.actionKey ).set ( action );
	}
	
	public <T> T syncWriteMessage ( String action, String message, T result ) {
		if ( message == null || action == null ) {
			return null;
		}
		if ( channel == null ) {
			connect ();
			if ( !isConnected () ) {
				return null;
			}
		}
		
		ioHandler.setSyncReadOperation ( result );
		
		ByteBuf writeBuf = channel.alloc ().buffer ( 64 * 1024, 1024 * 1024 );
		writeBuf.writeBytes ( action.getBytes () );
		writeBuf.writeBytes ( SocketConstants.ACTION_DATA_DELIMITER.getBytes () );
		writeBuf.writeBytes ( message.getBytes () );
		writeBuf.writeBytes ( ENCODER_DILIMITER.getBytes () );
		channel.writeAndFlush ( writeBuf ).syncUninterruptibly ();
		
		return result;
	}
	
	public static void main( String[] args ) {
		final DelegateClient client = new DelegateClient ( "api-v2.cframe.cn", 4689 );
		// final Client client = new Client ( "192.168.10.31", 1233 );
		new Thread () {
			@Override
			public void run() {
				client.connect ();
			}
		}.start ();
		
		while ( true ) {
			try {
				if ( client.isConnected () ) {
					break;
				}
				Thread.sleep ( 200 );
			} catch ( InterruptedException e ) {
				e.printStackTrace();
			}
		}
		// client.writeMessageWithAsync ( "/mobile/login:", "{username:'z',password='123',type='4',device='123'}" );
		String result = new String ();
		client.asyncWriteMessage ( "/mobile/login", "{username:'z',password='123',type='4',device='123'}" );
		// "/mobile/login:{username:'z',password='123',type='4',device='123'}"
		// "LO{\"type\":\"tencent\",\"token\":\"14E17A747651B4B2822826B28BFF6A03\"}"
	}
	
}
